package controller

import (
	"net/http"
	"recorder/control"
	"recorder/history"
	"recorder/internal"
	"recorder/log"
	"time"
)

type Controller struct {
	config  *internal.Config
	scanner *Scanner
	jobs    *Queue
	server  *control.Server
	results chan *internal.Result
	done    chan struct{}
	db      *history.Db
}

func New(config *internal.Config) *Controller {
	b := &Controller{
		config:  config,
		scanner: NewScanner(config),
		jobs:    NewQueue(),
		server:  control.NewServer(),
		results: make(chan *internal.Result),
		done:    make(chan struct{}),
		db:      history.NewDb(config),
	}
	return b
}

func (c *Controller) Close() {
	close(c.done)
}

func (c *Controller) fetchJobAndRun(w *Worker) {
	defer func() {
		w.Close()
	}()
	for {
		log.Info("worker %v fetch job", w.Id())
		fetchCancel := make(chan struct{})
		fetchResult := make(chan *JobFile)
		go func() {
			fetchResult <- c.jobs.Dequeue(fetchCancel)
		}()

		var f *JobFile

		select {
		case <-w.conn.Dis:
			log.Info("worker %v fetch job aborted: disconnected", w.Id())
			close(fetchCancel)
			<-fetchResult
			return
		case <-c.done:
			log.Info("worker %v fetch job aborted: shutdown", w.Id())
			close(fetchCancel)
			<-fetchResult
			return
		case f = <-fetchResult:
		}

		if f == nil {
			log.Warning("worker %v job fetched nil job???", w.Id())
			return
		} else {
			log.Info("worker %v fetch job %v", w.Id(), f.Job)
		}

		c.db.UpdateJob(&history.JobRecord{
			Id:         f.Job.Scene,
			UpdatedAt:  time.Now(),
			Controller: c.config.Id,
			Worker:     w.Id(),
			Status:     history.JobStatusRunning,
			Error:      "",
		})

		r, err := w.RunJob(f.Job)
		if err != nil {
			log.Warning("worker %v protocol error: %s", w.Id(), err.Error())
			c.jobs.EnqueueFront(f)
			c.db.UpdateJob(&history.JobRecord{
				Id:         f.Job.Scene,
				UpdatedAt:  time.Now(),
				Controller: c.config.Id,
				Worker:     "",
				Status:     history.JobStatusQueueing,
				Error:      "",
			})
			return
		}

		select {
		case <-c.done:
			log.Warning("job %s done but controller closed", f.Job.Scene)
			return
		case c.results <- r:
		}
		if len(r.Error) == 0 {
			c.db.UpdateJob(&history.JobRecord{
				Id:         f.Job.Scene,
				UpdatedAt:  time.Now(),
				Controller: c.config.Id,
				Worker:     w.Id(),
				Status:     history.JobStatusSuccess,
				Error:      "",
			})
		} else {
			c.db.UpdateJob(&history.JobRecord{
				Id:         f.Job.Scene,
				UpdatedAt:  time.Now(),
				Controller: c.config.Id,
				Worker:     w.Id(),
				Status:     history.JobStatusFail,
				Error:      r.Error,
			})
		}
	}
}

func (c *Controller) Run() {
	http.HandleFunc("/history", c.handleHistory)

	go c.server.Run(c.config)
	go c.scanner.Run(c.config)
	go c.db.Run()
	for {
		select {
		case <-c.done:
			log.Info("controller: closing")
			c.scanner.Close()
			c.server.Close()
			c.db.Close()
			log.Info("controller: closed")
			return
		case conn := <-c.server.C:
			log.Info("controller: new worker %v", conn.Id())
			w := NewWorker(conn)
			go c.fetchJobAndRun(w)
		case f := <-c.scanner.C:
			log.Info("controller: new job %v", f.Job)
			c.jobs.Enqueue(f)
			c.db.InsertJob(&history.JobRecord{
				Id:         f.Job.Scene,
				CreatedAt:  time.Now(),
				UpdatedAt:  time.Now(),
				Controller: c.config.Id,
				Worker:     "",
				Status:     history.JobStatusQueueing,
				Error:      "",
			})
		case r := <-c.results:
			log.Info("controller: done job %v err=[%v]", r.Job, r.Error)
			if len(r.Error) == 0 {
				c.scanner.BackupJob(r.Job, "success")
			} else {
				c.scanner.BackupJob(r.Job, "fail")
			}
		}
	}
}

func (c *Controller) handleHistory(w http.ResponseWriter, r *http.Request) {
	history.HandleHistory(w, r, c.db, getCurrentWorkerCount())
}
